package com.pxmeta.app.controller.auth;

import cn.dev33.satoken.annotation.SaCheckDisable;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.teaopenapi.models.Config;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Maps;
import com.pxmeta.circle.entity.Circle;
import com.pxmeta.circle.entity.UserCircle;
import com.pxmeta.circle.service.ICircleService;
import com.pxmeta.circle.service.IUserCircleService;
import com.pxmeta.common.core.domain.AjaxResult;
import com.pxmeta.common.core.domain.model.SessionUser;
import com.pxmeta.common.core.redis.RedisCache;
import com.pxmeta.common.exception.ServiceException;
import com.pxmeta.common.utils.CodeUtil;
import com.pxmeta.common.utils.SecurityUtils;
import com.pxmeta.common.utils.StringUtils;
import com.pxmeta.common.utils.auth.YunDunClient;
import com.pxmeta.common.utils.sign.Md5Utils;
import com.pxmeta.dynamic.service.RedisLockService;
import com.pxmeta.user.entity.User;
import com.pxmeta.user.entity.dto.*;
import com.pxmeta.user.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static com.pxmeta.common.constant.KiteConstant.DEFAULT_PASSWORD;
import static com.pxmeta.common.constant.RedisKeyConstants.TEST_ACCOUNT_KEY;

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Api(tags = "登录认证", value = "登录认证")
public class AuthController {

    @Value("${aliyun.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.accessKeySecret}")
    private String accessKeySecret;

    private static final String PHONE_CODE_KEY = "phone_code_key_%s";
    private final RedisCache redisCache;
    private final IUserService userService;
    private final IUserCircleService userCircleService;
    private final ICircleService circleService;
    private final RedisLockService redisLockService;
    private final YunDunClient yunDunClient;


    @SaCheckLogin
    @ApiOperation("绑定上下级关系")
    @GetMapping("/bindSubordinate")
    public AjaxResult bindSubordinate(@RequestParam String phone,
                                      @RequestParam Long parentUserId,
                                      @RequestParam String code){

        String finalCode = getPhoneCodeKey(phone);
        Set<String> testAccountSet = redisCache.getCacheSet(TEST_ACCOUNT_KEY);
        if (CollectionUtil.isNotEmpty(testAccountSet)){
            if (testAccountSet.contains(phone)){
                code = "1234";
            }
        }
        if (StringUtils.isBlank(finalCode)) {
            return AjaxResult.error("请获取验证码！");
        }
        if (!finalCode.equals(code)) {
            return AjaxResult.error("验证码无效！");
        }
        userService.bindUser(phone, DEFAULT_PASSWORD, parentUserId);

        return AjaxResult.success();
    }
    @ApiOperation("获取手机验证码")
    @GetMapping("/getPhoneCode")
    @ApiImplicitParams(
            value = {
                    @ApiImplicitParam(value = "手机号", name = "phone")
            }

    )
    public AjaxResult getPhoneCode(@RequestParam String phone) {

        String sendCodeLockKey = "sendCodeLockKey_" + phone;
        String lock = redisLockService.lock(sendCodeLockKey, 1000, 200);
        if (StringUtils.isNotBlank(lock)){
            String key = String.format(PHONE_CODE_KEY, phone);
            String code = StringUtils.getVerifyCode();
            redisCache.setCacheObject(key, code, 5, TimeUnit.MINUTES);
            try {
                sendSms(code, phone);
            } catch (Exception e) {
                return AjaxResult.error("短信发送失败！");
            }
            return AjaxResult.success("发送成功！");
        }
        return AjaxResult.error("请勿重复发送！");
    }

    @ApiOperation("校验验证码")
    @GetMapping("/verifyCode")
    public AjaxResult verifyCode(@RequestParam String phone,
                                 @RequestParam String phoneCode){
        String code = getPhoneCodeKey(phone);
        Set<String> testAccountSet = redisCache.getCacheSet(TEST_ACCOUNT_KEY);
        if (CollectionUtil.isNotEmpty(testAccountSet)){
            if (testAccountSet.contains(phone)){
                code = "1234";
            }
        }
        if (StringUtils.isBlank(code)) {
            return AjaxResult.error("请获取验证码！");
        }
        if (!code.equals(phoneCode)) {
            return AjaxResult.error("验证码无效！");
        }
        return AjaxResult.success();
    }

    private String getPhoneCodeKey(String phone) {
        String key = String.format(PHONE_CODE_KEY, phone);
        return redisCache.getCacheObject(key);
    }

    private void sendSms(String code, String phone) throws Exception {
        Config config = new Config()
                // 您的AccessKey ID
                .setAccessKeyId(accessKeyId)
                // 您的AccessKey Secret
                .setAccessKeySecret(accessKeySecret);
        // 访问的域名
        config.endpoint = "dysmsapi.aliyuncs.com";
        Client client = new Client(config);
        SendSmsRequest sendSmsRequest = new SendSmsRequest()
                .setSignName("碰心科技")
                .setTemplateCode("SMS_190267073")
                .setPhoneNumbers(phone)
                .setTemplateParam(String.format("{\"code\":\"%s\"}", code));
        // 复制代码运行请自行打印 API 的返回值
        client.sendSms(sendSmsRequest);
    }

    @ApiOperation("微信一键登录")
    @PostMapping("/app/wx/login")
    public AjaxResult loginByWx(@RequestBody WxAuthUserDTO dto){
        String lockKey = "wxLoginLock:"+dto.getUnionId();
        String lock = redisLockService.lock(lockKey, 1000, 200);

        if (StringUtils.isNotBlank(lock)){
            User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getWxUnionId, dto.getUnionId()));
            if (user == null) {
                user = userService.saveUserByWx(dto.getNickname(),dto.getUnionId(), DEFAULT_PASSWORD,dto.getAvatar());
            }
            Map<String, Object> map = login(user);

            return AjaxResult.success(map);
        }

        return AjaxResult.error("登录频繁，请勿重复登录！");
    }


    @ApiOperation("苹果一键登录")
    @PostMapping("/app/apple/login")
    public AjaxResult loginByApple(@RequestBody AppleAuthUserDTO dto){
        String lockKey = "appleLoginLock:"+dto.getAppleId();
        String lock = redisLockService.lock(lockKey, 1000, 200);

        if (StringUtils.isNotBlank(lock)){
            User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getAppleId, dto.getAppleId()));
            if (user == null) {
                user = userService.saveUserByApple(dto.getAppleId(), DEFAULT_PASSWORD);
            }
            Map<String, Object> map = login(user);

            return AjaxResult.success(map);
        }

        return AjaxResult.error("登录频繁，请勿重复登录！");
    }


    @GetMapping("/getUserTokenById")
    public AjaxResult getUserTokenById(@RequestParam String account){
        User user = userService.getByAccount(account);
        if (null == user){
            return AjaxResult.error("用户不存在");
        }
        Map<String, Object> map = login(user);
        return AjaxResult.success(map);
    }



    @ApiOperation("验证码登录")
    @PostMapping("/app/login")
    public AjaxResult login(@Validated @RequestBody AuthUserDTO authUser) {
        String code = getPhoneCodeKey(authUser.getPhone());
        Set<String> testAccountSet = redisCache.getCacheSet(TEST_ACCOUNT_KEY);
        if (CollectionUtil.isNotEmpty(testAccountSet)){
            if (testAccountSet.contains(authUser.getPhone())){
                code = "1234";
            }
        }
        if (StringUtils.isBlank(code)) {
            return AjaxResult.error("请获取验证码！");
        }
        if (!code.equals(authUser.getCode())) {
            return AjaxResult.error("验证码无效！");
        }
        User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getPhone, authUser.getPhone()));
        if (user == null) {
            user = userService.saveUser(authUser.getPhone(), DEFAULT_PASSWORD);
        }
        Map<String, Object> map = login(user);
        return AjaxResult.success(map);
    }


    @ApiOperation("手机号码一键登录")
    @PostMapping("/app/phoneOneClickLogin")
    public AjaxResult phoneOneClickLogin(@RequestBody OneClickAuthDTO dto){
        String phone = yunDunClient.checkGetPhone(dto.getToken(), dto.getAccessToken());
        User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getPhone, phone));
        if (user == null) {
            user = userService.saveUser(phone, DEFAULT_PASSWORD);
        }
        Map<String, Object> map = login(user);
        return AjaxResult.success(map);
    }

    @SaCheckLogin
    @ApiOperation("初始化密码")
    @PostMapping("/app/password/init")
    public AjaxResult initPassword(@Validated @RequestBody PasswordInitDTO passwordInitDTO) {
        String password = passwordInitDTO.getPassword();
        if (password.length() < 6) {
            return AjaxResult.error("密码长度不能小于6位！");
        }
        User user = userService.getById(SecurityUtils.getUserId());
        user.setPassword(Md5Utils.hash(password));
        userService.updateById(user);
        return AjaxResult.success("密码初始化成功！");
    }

    /**
     * 绑定手机号
     * @return
     */
    @PostMapping("/boundPhone")
    @SaCheckLogin
    @ApiOperation("绑定手机号")
    public AjaxResult boundPhone(@RequestBody BoundPhoneDTO dto){
        //绑定手机号
        String phone = dto.getPhone();
        String code = getPhoneCodeKey(phone);

        Set<String> testAccountSet = redisCache.getCacheSet(TEST_ACCOUNT_KEY);
        if (CollectionUtil.isNotEmpty(testAccountSet)){
            if (testAccountSet.contains(phone)){
                code = "1234";
            }
        }
        if (StringUtils.isBlank(code)) {
            return AjaxResult.error("请获取验证码！");
        }
        if (!code.equals(dto.getCode())) {
            return AjaxResult.error("验证码无效！");
        }
        Boolean exist = userService.getUserExistByPhone(phone);
        if (exist){
            return AjaxResult.error("该手机号已被绑定！");
        }
        Long userId = SecurityUtils.getUserId();
        User user = userService.getById(userId);
        user.setPhone(phone);
        userService.updateById(user);

        return AjaxResult.success();
    }


    @ApiOperation("密码登录")
    @PostMapping("/app/password/login")
    public AjaxResult login(@Validated @RequestBody LoginUserDTO loginUser) {
        if (StringUtils.isBlank(loginUser.getPassword())) {
            return AjaxResult.error("密码不能为空！");
        }
        //用户名,密码登录
        User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getPhone, loginUser.getUsername()).eq(User::getPassword, Md5Utils.hash(loginUser.getPassword())));
        if (user == null) {
            return AjaxResult.error("用户名或密码错误！");
        }
        Map<String, Object> map = login(user);
        return AjaxResult.success(map);
    }

    @ApiOperation("修改密码")
    @PutMapping("/app/password/modify")
    public AjaxResult login(@Validated @RequestBody PasswordModifyDTO modifyPassword) {
        User user = userService.getByPhone(modifyPassword.getPhone());
        if (modifyPassword.getNewPassword().length() < 6) {
            return AjaxResult.error("密码长度不能小于6位！");
        }
        user.setPassword(Md5Utils.hash(modifyPassword.getNewPassword()));
        userService.updateById(user);
        return AjaxResult.success("密码修改成功！");
    }

    /**
     * 初始化默认圈子
     *
     * @param userId
     */
    private void initSysCircle(Long userId) {
        //初始化默认圈子
        Circle systemCircle = circleService.getSystemCircle();
        UserCircle userCircle = new UserCircle();
        userCircle.setUserId(userId);
        userCircle.setCircleId(systemCircle.getId());
        userCircle.setSort(99999);
        userCircleService.joinCircle(userCircle, true);
    }

    @SaCheckLogin
    @ApiOperation("退出登录")
    @DeleteMapping("/logout")
    public AjaxResult logout() {
        StpUtil.logout();
        return AjaxResult.success();
    }


    @SaCheckLogin
    @ApiOperation("注销账号")
    @DeleteMapping("/cancelAccount")
    public AjaxResult cancelAccount(@RequestParam String phoneCode) {
        Long userId = SecurityUtils.getUserId();
        User user = userService.getById(userId);
        if (user == null) {
            return AjaxResult.error("用户不存在！");
        }
        String code = getPhoneCodeKey(user.getPhone());
        if (StringUtils.isBlank(phoneCode) || !code.equals(phoneCode)) {
            return AjaxResult.error("验证码无效！");
        }
        StpUtil.logout();
        userService.cancelAccount(userId);
        return AjaxResult.success();
    }

    private Map<String, Object> login(User user) {
        StpUtil.login(user.getId(), "APP");
        SessionUser sessionUser = new SessionUser();
        BeanUtils.copyProperties(user, sessionUser);
        StpUtil.getSession().set("user", sessionUser);
        SaTokenInfo token = StpUtil.getTokenInfo();
        HashMap<String, Object> map = Maps.newHashMap();
//        boolean initCircleStat = false;
        LambdaQueryWrapper<UserCircle> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(UserCircle::getUserId, user.getId());
//        long count = userCircleService.count(wrapper);
//        if (count == 0) {
////            initSysCircle(user.getId());
//        } else if (count > 1) {
//            initCircleStat = true;
//        }
        map.put("im_pwd", Md5Utils.hash(DEFAULT_PASSWORD));
        token.setTokenValue("Bearer " + token.getTokenValue());
        map.put("token", token);
        map.put("user", user);
        map.put("initCircleStat", false);

        this.checkDisabled(user.getId());

        if (StringUtils.isBlank(user.getInviteCode())){
            LambdaQueryWrapper<User> userWrapper = Wrappers.lambdaQuery();
            String code = "";
            do {
                userWrapper.clear();
                code = CodeUtil.generateInviteCode();
                userWrapper.eq(User::getInviteCode, code);
            }while (userService.getOne(userWrapper)!=null);
            user.setInviteCode(code);
            userService.updateById(user);
        }

        return map;
    }


    private void checkDisabled(Long userId){
        double disableTime = StpUtil.getDisableTime(userId);
        if (disableTime==-2){
            return;
        }
        if (disableTime==-1){
            //永久封禁
            StpUtil.logout(userId);
            throw new ServiceException("账号已被永久封禁！");
        }else {
            double l = BigDecimal.valueOf(disableTime / 60).setScale(1, RoundingMode.HALF_UP).doubleValue();
            if (l>60){
                double l1 = BigDecimal.valueOf(l / 60).setScale(1, RoundingMode.HALF_UP).doubleValue();
                if (l1>24){
                    //天
                    double l2 = BigDecimal.valueOf(l1 / 24).setScale(1, RoundingMode.HALF_UP).doubleValue();
                    StpUtil.logout(userId);
                    throw new ServiceException("账号已被封禁！剩余："+l2+"天");
                }else {
                    //小时
                    StpUtil.logout(userId);
                    throw new ServiceException("账号已被封禁！剩余："+l1+"小时");
                }
            }else {
                //分钟
                StpUtil.logout(userId);
                throw new ServiceException("账号已被封禁！剩余："+l+"分钟");
            }
        }
    }
}
